// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 软件包操作系统为操作系统提供独立于平台的接口
// 功能。虽然错误处理是
// Go like，但设计是类似Unix的；失败的调用返回错误类型的值，而不是错误号。
// 通常情况下，错误中会提供更多信息。例如，
// 如果采用文件名的调用失败，例如Open或Stat，则错误
// 在打印时将包括失败的文件名，并且类型为
// /*PathError，有关详细信息，可以将其解压缩。
// 
// 操作系统接口旨在跨所有操作系统实现统一。
// 特定于系统的软件包syscall中会出现通常不可用的功能。
// 
// 下面是一个简单的示例，打开一个文件并读取其中的一些内容。
// 
// 文件，错误：=os.Open（“file.go”）
// 如果出错！=nil{
// /log.Fatal（err）
// }
// 
// 如果打开失败，错误字符串将是不言自明的，就像
// 
// open file.go：没有这样的文件或目录
// 
// 然后可以将文件的数据读入字节片中。Read和
// Write从参数片的长度中获取字节计数。
// 
// 数据：=生成（[]字节，100）
// 计数，错误：=文件读取（数据）
// 如果错误！=nil{
// log.Fatal（err）
// }
// /fmt.Printf（“读取%d字节：%q\n”，计数，数据[：计数]）
// 
// 注意：操作系统或操作系统可能会限制文件上并发操作的最大数量。数字应该很高，但超过该数字可能会降低
// 性能或导致其他问题。
// 
package os

import (
	"errors"
	"internal/poll"
	"internal/testlog"
	"internal/unsafeheader"
	"io"
	"io/fs"
	"runtime"
	"syscall"
	"time"
	"unsafe"
)

// Name返回要打开的文件的名称。
func (f *File) Name() string { return f.name }

// Stdin、Stdout和Stderr是指向标准输入、
// 标准输出和标准错误文件描述符的打开文件。
// 
// 请注意，Go运行时会写入标准错误，以防出现恐慌和崩溃；
// 关闭Stderr可能会导致这些消息转到其他位置，可能
// 转到稍后打开的文件。
var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

// 标记到OpenFile，用于包装底层系统的标记。并非所有
// 标志都可以在给定系统上实现。
const (
	// 必须指定O_RDONLY、O_WRONLY或O_RDWR中的一个。
	O_RDONLY int = syscall.O_RDONLY // 以只读方式打开文件。
	O_WRONLY int = syscall.O_WRONLY // 仅以写方式打开文件。
	O_RDWR   int = syscall.O_RDWR   // 以读写方式打开文件。
	// 剩余的值可以被输入或输入以控制行为。
	O_APPEND int = syscall.O_APPEND // 写入时将数据追加到文件中。
	O_CREATE int = syscall.O_CREAT  // 如果不存在文件，请创建一个新文件。
	O_EXCL   int = syscall.O_EXCL   // 与O_CREATE一起使用，文件不能存在。
	O_SYNC   int = syscall.O_SYNC   // 为同步I/O打开。
	O_TRUNC  int = syscall.O_TRUNC  // 打开时截断常规可写文件。
)

// 查找值的来源。
// 
// 已弃用：使用io.SeekStart、io.SeekCurrent和io.SeekEnd。
const (
	SEEK_SET int = 0 // 相对于文件源的seek 
	SEEK_CUR int = 1 // 相对于当前偏移量的seek 
	SEEK_END int = 2 // 相对于结束的seek 
)

// 链接器在链接或符号链接或重命名
// 系统调用期间记录错误以及导致错误的路径。
type LinkError struct {
	Op  string
	Old string
	New string
	Err error
}

func (e *LinkError) Error() string {
	return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error()
}

func (e *LinkError) Unwrap() error {
	return e.Err
}

// Read从文件中最多读取len（b）个字节。
// 返回读取的字节数和遇到的任何错误。
// 在文件末尾，Read返回0，io.EOF。
func (f *File) Read(b []byte) (n int, err error) {
	if err := f.checkValid("read"); err != nil {
		return 0, err
	}
	n, e := f.read(b)
	return n, f.wrapErr("read", e)
}

// ReadAt从文件中读取len（b）字节，从字节偏移量off开始。
// 返回读取的字节数和错误（如果有）。
// ReadAt在n<len（b）时始终返回非零错误。
// 在文件末尾，该错误是io.EOF。
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
	if err := f.checkValid("read"); err != nil {
		return 0, err
	}

	if off < 0 {
		return 0, &PathError{Op: "readat", Path: f.name, Err: errors.New("negative offset")}
	}

	for len(b) > 0 {
		m, e := f.pread(b, off)
		if e != nil {
			err = f.wrapErr("read", e)
			break
		}
		n += m
		b = b[m:]
		off += int64(m)
	}
	return
}

// ReadFrom实现io.ReaderFrom。
func (f *File) ReadFrom(r io.Reader) (n int64, err error) {
	if err := f.checkValid("write"); err != nil {
		return 0, err
	}
	n, handled, e := f.readFrom(r)
	if !handled {
		return genericReadFrom(f, r) // 不包装
	}
	return n, f.wrapErr("write", e)
}

func genericReadFrom(f *File, r io.Reader) (int64, error) {
	return io.Copy(onlyWriter{f}, r)
}

type onlyWriter struct {
	io.Writer
}

// 将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
// 当n！=len（b）。
func (f *File) Write(b []byte) (n int, err error) {
	if err := f.checkValid("write"); err != nil {
		return 0, err
	}
	n, e := f.write(b)
	if n < 0 {
		n = 0
	}
	if n != len(b) {
		err = io.ErrShortWrite
	}

	epipecheck(f, e)

	if e != nil {
		err = f.wrapErr("write", e)
	}

	return n, err
}

var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND")

// WriteAt从字节偏移量off开始将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
// 当n！=len（b）。
// 
// 如果文件是使用O_APPEND标志打开的，WriteAt将返回错误。
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
	if err := f.checkValid("write"); err != nil {
		return 0, err
	}
	if f.appendMode {
		return 0, errWriteAtInAppendMode
	}

	if off < 0 {
		return 0, &PathError{Op: "writeat", Path: f.name, Err: errors.New("negative offset")}
	}

	for len(b) > 0 {
		m, e := f.pwrite(b, off)
		if e != nil {
			err = f.wrapErr("write", e)
			break
		}
		n += m
		b = b[m:]
		off += int64(m)
	}
	return
}

// Seek将下一次读取或写入文件的偏移量设置为偏移量，解释
// 根据其位置：0表示相对于文件原点，1表示相对于当前偏移量，2表示相对于结束。
// 返回新的偏移量和错误（如果有）。
// 未指定使用O_APPEND打开的文件上的Seek行为。
// 
// 如果f是一个目录，则Seek的行为因操作
// 系统而异；您可以在Unix操作系统（如
// 操作系统）上查找目录的开头，但在Windows上不能。
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
	if err := f.checkValid("seek"); err != nil {
		return 0, err
	}
	r, e := f.seek(offset, whence)
	if e == nil && f.dirinfo != nil && r != 0 {
		e = syscall.EISDIR
	}
	if e != nil {
		return 0, f.wrapErr("seek", e)
	}
	return r, nil
}

// WriteString类似于Write，但写入字符串s的内容，而不是
// 一段字节。
func (f *File) WriteString(s string) (n int, err error) {
	var b []byte
	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b))
	hdr.Data = (*unsafeheader.String)(unsafe.Pointer(&s)).Data
	hdr.Cap = len(s)
	hdr.Len = len(s)
	return f.Write(b)
}

// Mkdir使用指定的名称和权限创建一个新目录
// bits（在umask之前）。
// 如果有错误，则类型为*PathError。
func Mkdir(name string, perm FileMode) error {
	if runtime.GOOS == "windows" && isWindowsNulName(name) {
		return &PathError{Op: "mkdir", Path: name, Err: syscall.ENOTDIR}
	}
	longName := fixLongPath(name)
	e := ignoringEINTR(func() error {
		return syscall.Mkdir(longName, syscallMode(perm))
	})

	if e != nil {
		return &PathError{Op: "mkdir", Path: name, Err: e}
	}

	// mkdir（2）本身不会处理*BSD和Solaris上的粘性位
	if !supportsCreateWithStickyBit && perm&ModeSticky != 0 {
		e = setStickyBit(name)

		if e != nil {
			Remove(name)
			return e
		}
	}

	return nil
}

// setStickyBit将适度性添加到path的权限位，非原子性。
func setStickyBit(name string) error {
	fi, err := Stat(name)
	if err != nil {
		return err
	}
	return Chmod(name, fi.Mode()|ModeSticky)
}

// Chdir将当前工作目录更改为命名目录。
// 如果有错误，则类型为*PathError。
func Chdir(dir string) error {
	if e := syscall.Chdir(dir); e != nil {
		testlog.Open(dir) // 观察可能不存在的目录
		return &PathError{Op: "chdir", Path: dir, Err: e}
	}
	if log := testlog.Logger(); log != nil {
		wd, err := Getwd()
		if err == nil {
			log.Chdir(wd)
		}
	}
	return nil
}

// 打开打开命名文件进行读取。如果成功，
// 上的方法返回的文件可以读取；关联的文件
// 描述符只有模式O_RDONLY。
// 如果有错误，则类型为*PathError。
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

// Create创建或截断命名文件。如果该文件已存在，则
// 该文件将被截断。如果该文件不存在，则使用0666 
// /（umask之前）模式创建该文件。如果成功，返回文件上的方法可以
// 用于I/O；关联的文件描述符具有模式O_RDWR。
// 如果有错误，则类型为*PathError。
func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

// OpenFile是广义的open调用；大多数用户将使用Open 
// 或Create。它使用指定的标志
// /（Orduonly等）打开命名文件。如果文件不存在，并且传递了O_CREATE标志
// 时，将使用mode perm（在umask之前）创建该文件。如果成功，返回文件上的
// 方法可用于I/O。
// 如果有错误，则类型为*PathError。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	testlog.Open(name)
	f, err := openFileNolog(name, flag, perm)
	if err != nil {
		return nil, err
	}
	f.appendMode = flag&O_APPEND != 0

	return f, nil
}

// lstat在测试中被覆盖。
var lstat = Lstat

// 重命名将旧路径重命名（移动）为新路径。
// 如果newpath已经存在并且不是目录，则重命名将替换它。
// 当oldpath和newpath位于不同的目录中时，可能会应用特定于操作系统的限制。
// 如果出现错误，则类型为*LinkError。
func Rename(oldpath, newpath string) error {
	return rename(oldpath, newpath)
}

// 包syscall中的许多函数返回的计数为-1，而不是0。
// 使用fixCount（call（））代替call（）可更正计数。
func fixCount(n int, err error) (int, error) {
	if n < 0 {
		n = 0
	}
	return n, err
}

// wrapErr包装在对打开的文件执行操作期间发生的错误。
// 它通过unchanged传递io.EOF，否则将
// poll.ErrFileClosing转换为errclosing，并将错误包装在PathError中。
func (f *File) wrapErr(op string, err error) error {
	if err == nil || err == io.EOF {
		return err
	}
	if err == poll.ErrFileClosing {
		err = ErrClosed
	}
	return &PathError{Op: op, Path: f.name, Err: err}
}

// TempDir返回用于临时文件的默认目录。
// 
// 在Unix系统上，如果非空，则返回$TMPDIR，否则返回/tmp。
// 在Windows上，它使用GetTempPath，从%TMP%、%TEMP%、%USERPROFILE%或Windows目录返回第一个非空
// 值。
// 在计划9上，它返回/tmp。
// 
// 该目录既不保证存在，也不具有可访问的权限
// 权限。
func TempDir() string {
	return tempDir()
}

// UserCacheDir返回用于用户特定
// 缓存数据的默认根目录。用户应在此目录中创建自己的特定于应用程序的子目录
// 并使用该子目录。
// 
// 在Unix系统上，它返回
// specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html 如果
// 非空，则为$HOME/.cache。
// 在达尔文，它返回$HOME/Library/Caches。
// 在Windows上，它返回%LocalAppData%。
// 在计划9中，它返回$home/lib/cache。
// 
// 如果无法确定位置（例如未定义$HOME），
// 则返回错误。
func UserCacheDir() (string, error) {
	var dir string

	switch runtime.GOOS {
	case "windows":
		dir = Getenv("LocalAppData")
		if dir == "" {
			return "", errors.New("%LocalAppData% is not defined")
		}

	case "darwin", "ios":
		dir = Getenv("HOME")
		if dir == "" {
			return "", errors.New("$HOME is not defined")
		}
		dir += "/Library/Caches"

	case "plan9":
		dir = Getenv("home")
		if dir == "" {
			return "", errors.New("$home is not defined")
		}
		dir += "/lib/cache"

	default: // Unix 
		dir = Getenv("XDG_CACHE_HOME")
		if dir == "" {
			dir = Getenv("HOME")
			if dir == "" {
				return "", errors.New("neither $XDG_CACHE_HOME nor $HOME are defined")
			}
			dir += "/.cache"
		}
	}

	return dir, nil
}

// UserConfigDir返回用于用户特定
// 配置数据的默认根目录。用户应创建自己的特定于应用程序的
// 子目录，并使用该子目录。
// 
// 在Unix系统上，它返回
// specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html 如果
// 非空，则为$HOME/.config。
// 在Darwin上，它返回$HOME/Library/Application支持。
// 在Windows上，它返回%AppData%。
// 在计划9中，它返回$home/lib。
// 
// 如果无法确定位置（例如未定义$HOME），
// 则返回错误。
func UserConfigDir() (string, error) {
	var dir string

	switch runtime.GOOS {
	case "windows":
		dir = Getenv("AppData")
		if dir == "" {
			return "", errors.New("%AppData% is not defined")
		}

	case "darwin", "ios":
		dir = Getenv("HOME")
		if dir == "" {
			return "", errors.New("$HOME is not defined")
		}
		dir += "/Library/Application Support"

	case "plan9":
		dir = Getenv("home")
		if dir == "" {
			return "", errors.New("$home is not defined")
		}
		dir += "/lib"

	default: // Unix 
		dir = Getenv("XDG_CONFIG_HOME")
		if dir == "" {
			dir = Getenv("HOME")
			if dir == "" {
				return "", errors.New("neither $XDG_CONFIG_HOME nor $HOME are defined")
			}
			dir += "/.config"
		}
	}

	return dir, nil
}

// UserHomeDir返回当前用户的主目录。
// 
// 在Unix（包括macOS）上，它返回$HOME环境变量。
// 在Windows上，它返回%USERPROFILE%。
// 在计划9中，它返回$home环境变量。
func UserHomeDir() (string, error) {
	env, enverr := "HOME", "$HOME"
	switch runtime.GOOS {
	case "windows":
		env, enverr = "USERPROFILE", "%userprofile%"
	case "plan9":
		env, enverr = "home", "$home"
	}
	if v := Getenv(env); v != "" {
		return v, nil
	}
	// 在某些鹅上，主目录并不总是定义的。
	switch runtime.GOOS {
	case "android":
		return "/sdcard", nil
	case "ios":
		return "/", nil
	}
	return "", errors.New(enverr + " is not defined")
}

// Chmod将命名文件的模式更改为模式。
// 如果文件是符号链接，则会更改链接目标的模式。
// 如果有错误，则类型为*PathError。
// 
// 根据
// 操作系统，使用不同的模式位子集。
// 
// 在Unix上，使用模式的权限位、ModeSetuid、ModeSetgid和
// ModeSticky。
// 
// 在Windows上，仅使用模式的0200位（所有者可写）；它控制文件的只读属性是设置还是清除。
// 其他位当前未使用。为了与Go 1.12 
// /及更早版本兼容，请使用非零模式。只读
// 文件使用模式0400，可读+可写文件使用模式0600。
// 
// 在计划9中，使用了模式的权限位、ModeAppend、ModeExclusive、
// 和ModeTemporary。
func Chmod(name string, mode FileMode) error { return chmod(name, mode) }

// Chmod将文件的模式更改为模式。
// 如果有错误，则类型为*PathError。
func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) }

// 设置文件的读写截止日期。
// 这相当于同时调用SetReadDeadline和SetWriteDeadline。
// 
// 只有某些类型的文件支持设置截止日期。对不支持截止日期的文件调用setDaildate 
// 将返回ErrNoDeadline。
// 在大多数系统上，普通文件不支持截止日期，但管道支持。
// 
// 截止日期是I/O操作失败的绝对时间，在此之后出现错误而不是阻塞。截止日期适用于所有未来的和未决的
// I/O，而不仅仅是紧接着的读或写调用。
// 超过截止日期后，可以通过在将来设置截止日期来刷新连接
// 。
// 
// 如果超过了截止日期，则对读或写或其他I/O的调用
// 方法将返回一个错误，该错误超过了死线。
// 这可以使用errors.Is（err，os.errdeadlineextended）进行测试。
// 该错误实现了Timeout方法，调用Timeout 
// 方法将返回true，但对于其他可能的错误，
// 即使未超过截止日期，超时也将返回true。
// 
// 在读写调用成功后，可以通过重复延长
// 截止时间来实现空闲超时。
// 
// t的零值表示I/O操作不会超时。
func (f *File) SetDeadline(t time.Time) error {
	return f.setDeadline(t)
}

// SetReadDeadline设置未来读取调用和任何
// 当前阻止的读取调用的截止日期。
// t的零值表示读取不会超时。
// 并非所有文件都支持设置截止日期；见最后期限。
func (f *File) SetReadDeadline(t time.Time) error {
	return f.setReadDeadline(t)
}

// SetWriteDadline设置任何未来写入调用和任何
// 当前阻止的写入调用的截止日期。
// 即使写入超时，也可能返回n>0，表示
// 部分数据写入成功。
// t的零值表示写入不会超时。
// 并非所有文件都支持设置截止日期；见最后期限。
func (f *File) SetWriteDeadline(t time.Time) error {
	return f.setWriteDeadline(t)
}

// SyscallConn返回一个原始文件。
// 这实现了syscall.Conn接口。
func (f *File) SyscallConn() (syscall.RawConn, error) {
	if err := f.checkValid("SyscallConn"); err != nil {
		return nil, err
	}
	return newRawConn(f)
}

// isWindowsNulName报告Windows上的名称是否为os.DevNull（'NUL'）。
// 如果name为'NUL'，则无论何种情况，都返回True。
func isWindowsNulName(name string) bool {
	if len(name) != 3 {
		return false
	}
	if name[0] != 'n' && name[0] != 'N' {
		return false
	}
	if name[1] != 'u' && name[1] != 'U' {
		return false
	}
	if name[2] != 'l' && name[2] != 'L' {
		return false
	}
	return true
}

// DirFS返回一个文件系统（一个fs.fs），用于目录dir中根目录下的文件树。请注意，DirFS（“/prefix”）只保证它对操作系统的Open调用将以“/prefix”：DirFS（“/prefix”）开头。Open（“file”）是与os.Open（“/prefix/file”）相同的
// 。因此，如果/prefix/file是指向/prefix树之外的符号链接，那么使用DirFS不会像使用
// os.Open那样停止访问。因此，当目录树包含任意内容时，DirFS不是chroot风格的安全机制
// 的一般替代品。
func DirFS(dir string) fs.FS {
	return dirFS(dir)
}

func containsAny(s, chars string) bool {
	for i := 0; i < len(s); i++ {
		for j := 0; j < len(chars); j++ {
			if s[i] == chars[j] {
				return true
			}
		}
	}
	return false
}

type dirFS string

func (dir dirFS) Open(name string) (fs.File, error) {
	if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
		return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
	}
	f, err := Open(string(dir) + "/" + name)
	if err != nil {
		return nil, err // nil fs.File 
	}
	return f, nil
}

func (dir dirFS) Stat(name string) (fs.FileInfo, error) {
	if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
		return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid}
	}
	f, err := Stat(string(dir) + "/" + name)
	if err != nil {
		return nil, err
	}
	return f, nil
}

// ReadFile读取命名文件并返回内容。
// 成功的调用返回err==nil，而不是err==EOF。
// 因为ReadFile读取整个文件，所以它不会将读取
// 的EOF视为要报告的错误。
func ReadFile(name string) ([]byte, error) {
	f, err := Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var size int
	if info, err := f.Stat(); err == nil {
		size64 := info.Size()
		if int64(int(size64)) == size64 {
			size = int(size64)
		}
	}
	size++ // 一个字节用于EOF的最终读取

	// 如果文件大小较小，请至少读取512字节。
	// 特别是，Linux的/proc声明大小为0的文件，但如果以小块形式读取，则无法正常工作，因此，1字节的初始读取无法正常工作。
	if size < 512 {
		size = 512
	}

	data := make([]byte, 0, size)
	for {
		if len(data) >= cap(data) {
			d := append(data[:cap(data)], 0)
			data = d[:len(data)]
		}
		n, err := f.Read(data[len(data):cap(data)])
		data = data[:len(data)+n]
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return data, err
		}
	}
}

// WriteFile将数据写入命名文件，必要时创建该文件。
// 如果文件不存在，WriteFile将使用perm权限（在umask之前）创建该文件；
// 否则，WriteFile会在写入之前将其截断，而不会更改权限。
func WriteFile(name string, data []byte, perm FileMode) error {
	f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
	if err != nil {
		return err
	}
	_, err = f.Write(data)
	if err1 := f.Close(); err1 != nil && err == nil {
		err = err1
	}
	return err
}
